﻿using System.IO.Ports;
using System.Net.Sockets;
using System.Text;
using Zhy.ModbusLib;
using Zhy.ModbusLib.Base;

namespace Zhy.ModbusTest
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");

            //LibTest(); // EasyModbusTCP库操作 - 推荐使用库的方式

            //RTU03Test(); // 读取保持寄存器

            //RTU06Test(); // odbusRTU 写单个寄存器

            //RTU16Test(); // odbusRTU 写多个寄存器

            //RTUStringTest(); // ModbusRTU 写字符串寄存器

            //RTU01Test(); // ModbusRTU 读线圈消息

            RTU05Test(); // ModbusRTU 写单个线圈状态

            //RTU15Test(); // ModbusRTU 写多个线圈状态

            //RTUException(); // ModbusRTU 异常状态码处理

            //Ascii03Test(); // Ascii的报文发送与接收

            //TCP03Test(); // Modbus TCP协议接收和发送报文

            ZxLibTest(); // 封装后的协议测试方法

            Console.ReadLine();
        }

        #region Modbus库演示
        /// <summary>
        /// EasyModbusTCP库操作
        /// 使用Modbus库中已经将发送和接收的指令已经封装好了
        /// </summary>
        static void LibTest()
        {
            // COM1 采用串口方式连接
            EasyModbus.ModbusClient client = new EasyModbus.ModbusClient("COM2"); // 实例化Modbus通讯对象 
            client.Connect(); // 连接从端服务

            client.UnitIdentifier = 1; // 设置Slave ID 的地址
            int[] values = client.ReadHoldingRegisters(0, 10); // 读取0-3个保持寄存器数据

            bool[] bs = client.ReadCoils(0, 10); // 读取0-10个线圈状态
        }
        #endregion

        #region Modbus串口方式读取保持寄存器
        /// <summary>
        /// ModbusRTU 读取保持寄存器
        /// 使用串口方式发送和接收指令集 - 使用串口方式确实不如使用Modbus封装好的库好用
        /// </summary>
        static void RTU03Test()
        {
            SerialPort serialPort = new SerialPort("COM2"); // 串口通讯对象
            serialPort.Open(); // 打开串口

            #region 发送指令
            // 按照协议进行请求数据整理准备
            // 请求的10个寄存器指令集
            List<byte> bytes = new List<byte> 
            {
                0x01,  // 从站地址
                0x03,  // 功能码
                0x00, 0x00,  // 起始地址
                0x00, 0x0A  // 读取个数
            };
            // 通过CRC16方法进行校验码计算，将获取的校验码拼接到报文尾部
            var crc = CRC16(bytes.ToArray()); // 计算报文数据校验位
            bytes.AddRange(crc.Reverse()); // 添加报文校验位 - 反转是大端小端的问题

            serialPort.Write(bytes.ToArray(), 0, bytes.Count); // 发送指令集
            #endregion

            #region 接收指令
            //返回的指令实例 01（从站地址） 03（功能码） 14（数据字节长度） 00 6F 00 DE 01 4D 01 BC 02 2B 02 9A 03 09 03 78 03 E7 00 7B（数据部分） 3D 4A（CRC校验位）
            byte[] resp = new byte[25]; // 实例化要接收的指令 - 这个接收的指令个数可以通过请求指令计算出来。
            serialPort.Read(resp, 0, resp.Length); // 接收返回的指令
            #endregion

            #region 接收小数数据方式
            float fff = BitConverter.ToSingle(
               new byte[] { resp[3], resp[4], resp[5], resp[6] }.Reverse().ToArray(), 0); // 大端小数的解析方式
            //new byte[] { resp[3], resp[4], resp[5], resp[6] }, 0); // 小端小数的解析方式
            Console.WriteLine(fff);
            #endregion

            for (int i = 3; i < resp.Length-2; i = i + 2) // 循环读取寄存器指令
            {
                byte[] dataBytes=new byte[] { resp[i], resp[i+1] }; // 获取寄存器指令

                // Select  遍历每个字节，16进制的字符串
                // Join，把集合中的每项用  01 02 03 04 
                Console.WriteLine(string.Join(" ",dataBytes.Select(p=>p.ToString("X2")))); // 打印16进制

                // 2字节  C#  short/ushort/int16/uint16
                // 1000 0000    0000 0000
                //short value = (short)(dataBytes[0] * 256 + dataBytes[1]); // 16进制转10进制 - 可以满足有符号的情况

                //dataBytes.Reverse().ToArray()
                //将Modbus协议的中大端数据字节序颠倒顺序，适应BitConverter的小端字节序
                short value = BitConverter.ToInt16(dataBytes.Reverse().ToArray(), 0);// 小端  16进制转10进制

                Console.WriteLine($">> {value}"); // 打印
            }
        }
        #endregion

        #region odbusRTU 写单个寄存器
        /// <summary>
        /// ModbusRTU 写单个寄存器
        /// 只能写2个字节的数据
        /// </summary>
        static void RTU06Test() 
        {
            SerialPort serialPort = new SerialPort("COM2"); // 串口通讯对象
            serialPort.Open(); // 打开串口

            #region 发送指令
            // short ushort int16  uint16
            short value = 300;  // short 类型是2字节 16位
            // 2字节类型：short/ushort/int16/uint16        0-65535        -32768-32767 
            #region 写入值高位的计算公式解析
            // 256进行进位
            // 1  44
            // 1000 0001   0010 1100   // 300
            // 1111 1111   0000 0000   // 0xFF00
            // 0000 0001   0000 0000   // 位&操作
            // 0000 0000   0000 0001    // >> 8 (右移8位 - 低位的8位移除掉 只留高位)
            #endregion
            List<byte> bytes = new List<byte>
            {
                0x01,  // 从站地址
                0x06,  // 功能码
                0x00, 0x00,  // 起始地址
                (byte)((value & 0xFF00) >> 8), // 计算高位数值  -  这个位移8位必须是2字节，16位
                (byte)(value & 0x00FF),// 计算地位数值 写入的值   1  - 低位数值
            };
            // 通过CRC16方法进行校验码计算，将获取的校验码拼接到报文尾部
            var crc = CRC16(bytes.ToArray()); // 计算报文数据校验位
            bytes.AddRange(crc.Reverse()); // 添加报文校验位 - 反转是大端小端的问题
            // 发送写单个寄存器的请求
            serialPort.Write(bytes.ToArray(), 0, bytes.Count); // 发送指令集
            #endregion

        }
        #endregion

        #region ModbusRTU 写多个寄存器
        /// <summary>
        /// ModbusRTU 写多个寄存器
        /// </summary>
        static void RTU16Test()
        {
            // 通路链接起来
            SerialPort serialPort = new SerialPort("COM1"); // 串口通讯对象
            serialPort.Open(); // 打开连接

            float ff = 4.5f;  //float4个字节 这时候一次写多个寄存器
            byte[] v_bytes = BitConverter.GetBytes(ff); // 数据转byte数组
            
            List<byte> bytes = new List<byte>
            {
                0x01, // 从站地址
                0x10, // 功能码
                0x00,0x02, // 起始位置
                0x00,0x02,// 写入的寄存器地址数量
                0x04, // 写入的值的字节数
            };
            bytes.AddRange(v_bytes.Reverse()); //添加要写入的值 需要翻转   取决于设备
            byte[] crc = CRC16(bytes.ToArray()); // 计算报文数据的校验位
            bytes.AddRange(crc.Reverse()); // 添加报文数据的校验位，是否反转取决于设备的大小端

            // 发送写多个寄存器的请求
            serialPort.Write(bytes.ToArray(), 0, bytes.Count); // 发送报文数据

            // 正常/异常
            byte[] resp = new byte[8]; // 多个寄存器-返回8个字节
            serialPort.Read(resp, 0, resp.Length); // 读取返回的数据
        }
        #endregion

        #region ModbusRTU 写字符串寄存器
        /// <summary>
        /// ModbusRTU 写字符串寄存器
        /// </summary>
        static void RTUStringTest()
        {
            // 通路链接起来
            SerialPort serialPort = new SerialPort("COM2");
            serialPort.Open();

            string msg = "Hello Zhaoxi"; // 要发送的保持寄存器文本
            byte[] s_bytes = Encoding.ASCII.GetBytes(msg); // 将文本转成byte数组
            // 计算字符串的字节数量
            ushort r_count = (ushort)(Math.Ceiling(s_bytes.Length * 1.0 / 2));  // 每两个英文字母位一个字节

            // 需要翻转   取决于设备
            List<byte> bytes = new List<byte>
            {
                0x01, // 从站地址
                0x10, // 功能码
                0x00,0x08, // 起始位置
                (byte)(r_count/256),  // 写入寄存器地址数量 - 高位
                (byte)(r_count%256),// 写入的寄存器地址数量 - 地位
                (byte)s_bytes.Length, // 写入的值的字节数
            };
            bytes.AddRange(s_bytes); // 添加要写入的数据 - 文本数据不需要反转
            byte[] crc = CRC16(bytes.ToArray()); // 计算报文的校验位
            bytes.AddRange(crc.Reverse()); // 添加报文数据的校验位，是否反转取决于设备的大小端

            // 发送写多个寄存器的请求
            serialPort.Write(bytes.ToArray(), 0, bytes.Count);

            // 正常/异常
            byte[] resp = new byte[8];
            serialPort.Read(resp, 0, resp.Length); // 读取返回数据

            bytes = new List<byte>{
                0x01, // 从站地址
                0x03, // 功能码
                0x00, 0x08, // 起始位置
                0x00, 0x1E // 读取个数
            };
            // 通过CRC16方法进行校验码计算，将获取的校验码拼接到报文尾部
            crc = CRC16(bytes.ToArray()); // 计算报文数据的校验位
            bytes.AddRange(crc.Reverse());  // 添加报文数据的校验位，是否反转取决于设备的大小端

            serialPort.Write(bytes.ToArray(), 0, bytes.Count()); // 发送报文数据
            // 经测试此处读取不到30个寄存器

            //Encoding.ASCII.GetString(bytes.GetRange(3, 60).ToArray());
        }
        #endregion

        #region ModbusRTU 读线圈消息
        /// <summary>
        /// ModbusRTU 读线圈消息
        /// </summary>
        static void RTU01Test() 
        {
            // 通路链接起来
            SerialPort serialPort = new SerialPort("COM2");
            serialPort.Open();

            // 需要翻转   取决于设备
            List<byte> bytes = new List<byte>
            {
                0x01, // 从站地址
                0x01, // 功能码
                0x00,0x00, // 起始位置
                0x00,0x0A,// 读取线圈长度
            };
            byte[] crc = CRC16(bytes.ToArray()); // 计算报文的校验位
            bytes.AddRange(crc.Reverse()); // 添加报文数据的校验位，是否反转取决于设备的大小端

            // 发送写多个寄存器的请求
            serialPort.Write(bytes.ToArray(), 0, bytes.Count);
            Thread.Sleep(100);
            // 计算读取数据部分的长度是多少 - Math.Ceiling小数向上取整-取最大整数值
            int len = (int)(Math.Ceiling(10 * 1.0 / 8)); // 10是读取线圈长度 8是一个字节
            byte[] resp = new byte[len + 5]; // 5是固定的报文格式长度
            serialPort.Read(resp, 0, resp.Length); // 读取报文数据

            int count = 0; // 用于记录读取的线圈个数
            for (int i = 3; i < 3 + len; i++)  // 循环计算2进制线圈状态
            {
                // 取出每个字节中的位信息   0/1
                //bool s1 = (resp[i] & 1) > 0;   // 16进制&的时候会自动转成2进制  &是两个1得1
                //bool s2 = (resp[i] & 2) > 0;
                //bool s3 = (resp[i] & 4) > 0;
                //bool s4 = (resp[i] & 8) > 0;
                //bool s5 = (resp[i] & 16) > 0;
                //bool s6 = (resp[i] & 32) > 0;
                //bool s7 = (resp[i] & 64) > 0;
                //bool s8 = (resp[i] & 128) > 0;

                //Console.WriteLine($"{s1} - {s2} - {s3} - {s4} - {s5} - {s6} - {s7} - {s8}");

                // 这个循环方法等同于上面注释的代码
                for (int sit = 0; sit < 8; sit++) // 循环8次是一个字节8位
                {
                    // 16进制&的时候会自动转成2进制  &是两个1得1
                    bool state = (resp[i] & (1 << sit)) > 0;  // <<位移符号，2进制的1位移到第几位
                    count++;
                    if (count > 10)  // 10是线圈长度 - 当大于要读取的线圈长度后退出
                        return;
                    Console.WriteLine(state); // 打印线圈值
                }
            }
        }
        #endregion

        #region ModbusRTU 写单个线圈状态
        /// <summary>
        /// ModbusRTU 写单个线圈状态
        /// </summary>
        static void RTU05Test()
        {
            // 通路链接起来
            SerialPort serialPort = new SerialPort("COM2");
            serialPort.Open();

            List<byte> bytes = new List<byte>()
            {
                0x01, // 从站地址
                0x05, // 功能码
                0x00,0x01, // 起始地址
                0xFF,// 0xFF:on    0x00:off  要写入的线圈状态值
                0x00 // 要写入的线圈状态低位 固定值
            };
            byte[] crc = CRC16(bytes.ToArray()); // 计算报文的校验位
            bytes.AddRange(crc.Reverse()); // 添加报文的校验位 反转根据设备的大小端确定

            /// 把请求报文发送到设备
            serialPort.Write(bytes.ToArray(), 0, bytes.Count); // 发送报文数据

            // 读取的长度是多少
            byte[] resp = new byte[bytes.Count]; // 接收的报文与发送的报文相同
            serialPort.Read(resp, 0, resp.Length); // 读取返回的报文数据
        }
        #endregion

        #region ModbusRTU 写多个线圈状态
        /// <summary>
        /// ModbusRTU 写多个线圈状态
        /// </summary>
        static void RTU15Test()
        {
            // 通路链接起来
            SerialPort serialPort = new SerialPort("COM2");
            serialPort.Open();

            // 这里不涉及字节序的问题
            // 位状态的排序
            // 10 - 2
            // 1101 1111  0000 0010
            List<byte> bytes = new List<byte>()
            {
                0x01, // 从站地址
                0x0F, // 功能码
                0x00,0x00, // 起始地址
                0x00,0x0A, // 写入线圈的地址数量
                0x02,// 写入线圈状态的字节数量
                // 写入线圈的状态值
                0xDF, // 0-7的状态 2进制顺序是从右向左
                0x02  // 8-15的状态 2进制顺序是从右向左
            };
            byte[] crc = CRC16(bytes.ToArray()); // 计算报文的校验位
            bytes.AddRange(crc.Reverse()); // 添加报文的校验位 反转根据设备的大小端确定

            serialPort.Write(bytes.ToArray(), 0, bytes.Count); // 发送报文数据

            // 读取的长度是多少
            byte[] resp = new byte[8]; // 写多个的，固定返回8位
            serialPort.Read(resp, 0, resp.Length); // 读取报文数据 -
            // 读取报文数据的目的是判断写入是否成功
        }
        #endregion

        #region ModbusRTU 异常状态码处理
        /// <summary>
        /// ModbusRTU 异常状态码处理
        /// </summary>
        static void RTUException()
        {
            // 通路链接起来
            SerialPort serialPort = new SerialPort("COM1");
            serialPort.Open();

            List<byte> bytes = new List<byte>
            {
                0x01, // 从站地址
                0x01,/// 功能码
                0x00,0x00, // 起始地址
                0x00,0x0B // 
            };
            //byte[] crc = CRC16(bytes.ToArray());
            //bytes.AddRange(crc.Reverse());

            serialPort.Write(bytes.ToArray(), 0, bytes.Count);

            // 读取的长度是多少
            int len = (int)(Math.Ceiling(10 * 1.0 / 8));
            byte[] resp = new byte[len + 5];
            serialPort.Read(resp, 0, resp.Length); // 读取返回报文

            // 1、检查CRC对不对
            // 2、检查异常码
            if (resp[1] > 0x80) // 大于80的是异常状态码 - 需要后期详细研究一下
            {
                // 有异常
                //resp[2]
            }

            // 通信异常
        }
        #endregion

        #region 测试连续发送报文
        /// <summary>
        /// 测试连续发送报文
        /// </summary>
        static void RTUMonitor()
        {

            // 通路链接起来
            SerialPort serialPort = new SerialPort("COM1");
            serialPort.Open();

            while (true)
            {
                Thread.Sleep(200); // RTU的两个报文发送间隔要>=4毫秒，否者会出现粘包情况
                List<byte> bytes = new List<byte>{  // 要发送的指令
                    0x01, // 从站地址
                    0x03, // 功能码
                    0x00,0x00, // 起始地址
                    0x00,0x0A  // 要写入线圈的状态值
                };
                byte[] crc = CRC16(bytes.ToArray()); // 计算报文的校验位
                bytes.AddRange(crc.Reverse());  // 添加报文的校验位 反转根据设备的大小端确定

                serialPort.Write(bytes.ToArray(), 0, bytes.Count); // 发送报文数据
                // 
                byte[] resp = new byte[10 * 2 + 5]; // 计算接收报文长度
                serialPort.Read(resp, 0, resp.Length); // 读取报文

                Console.WriteLine(resp[3] * 256 + resp[4]); // 打印报文数据
            }

            // SerialPort   加锁
            // 报文队列   异步    写与读的报文优先级     通信库添加这
        }
        #endregion

        #region Modbus发送Ascii报文格式 - 目前只是了解
        /// <summary>
        /// Modbus发送Ascii报文格式
        /// </summary>
        static void Ascii03Test()
        {
            // 01 03 00 00 00 0A 00
            // ":01030000000A00\CR\LF"
            // 0x3A 0x30 0x31 0x30 0x33       .... 0x41...0x30  0x0D 0x0A
            // 以ModbusASCII报文格式进行保持型寄存器的读取，从0号地址开始读10个

            SerialPort serialPort = new SerialPort("COM2");
            serialPort.Open();

            List<byte> bytes = new List<byte>
            {
                0x01, // 地址
                0x03, // 功能码
                0x00,0x00, // 起始地址
                0x00,0x0A // 读取10个寄存器
            };

            // 1、LRC校验
            byte lrc = LRC(bytes.ToArray()); // 个字节校验码
            bytes.Add(lrc); // 添加校验么
            // 2、转字符串 - 表示将每个字节都转换成16进制的字符串 
            //16.ToString("X2")   -- "10"
            //1.ToString("X2")    -- "01"
            //15.ToString("X2")    -- "0F"
            // 将每个字节转换成16进制字符串
            string str = string.Join("", bytes.Select(b => b.ToString("X2")));
            // 3、拼接头和尾 - 固定格式
            str = ":" + str;
            List<byte> ascii = Encoding.ASCII.GetBytes(str).ToList(); // 将字符串转换成ASCII
            // 4、尾部添加固定格式
            ascii.Add(0x0D);
            ascii.Add(0x0A);

            serialPort.Write(ascii.ToArray(), 0, ascii.Count); // 发送报文数据

            // 解析的时候
            // 1、移除头尾
            // 2、从字符串转byte[]
            // 3、做相关检查：LRC校验   异常码检查    byte[]
            // 获取10个寄存器：（10*2（每个寄存器2个字节）+1个地址+1个功能码+1个字节数+1个校验位）*2）(每个字符2个ASCII)+1个头部+2个尾部=共51个
            byte[] resp = new byte[51];
            serialPort.Read(resp, 0, resp.Length); // 读取返回报文

            // 1、移除头尾
            ascii = resp.ToList().GetRange(1, resp.Length - 3);
            // 将ASCII码转成16进制的字符串-每2个位一组
            str = Encoding.ASCII.GetString(ascii.ToArray()); // 将ascii转换为字符串

            //"010314000B00160021002C003700000000000000000063E0"
            // 注意：Framework框架下，需要两两字符拼接转换
            // 将16进制字符串转换成16进制数组
            byte[] datas = Convert.FromHexString(str);

            for (int i = 0; i < 10; i++) // 读取10个寄存器
            {
                int start = (i * 2) + 3; // 从第3为开始读取
                byte[] dataBytes = new byte[] { datas[start], datas[start + 1] }; // 每2位是一个寄存器 

                short value = BitConverter.ToInt16(dataBytes.Reverse().ToArray(), 0);// 小端 将16进制转成19进制

                Console.WriteLine($">> {value}"); // 输出显示寄存器
            }
        }
        #endregion

        #region Modbus TCP协议接收和发送报文
        /// <summary>
        /// Modbus TCP协议接收和发送报文
        /// </summary>
        static void TCP03Test()
        {
            Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp); // 实例化Socket对象
            socket.Connect("127.0.0.1", 502); // 连接Modbus TCP服务

            byte[] bytes = new byte[] {
                0x00,0x00,// TransactionID （具体根据协议文档修改）
                0x00,0x00,// Modbus协议ID
                0x00,0x06,// 后续还有多少字节 - 大端字节序
                // RTU格式报文部分
                0x01, // 地址
                0x03, // 功能码
                0x00,0x00, // 起始位置
                0x00,0x0A // 读取寄存器个数
            };
            socket.Send(bytes); // 发送报文  

            #region 保证获取报文的完整性
            // 10 -> 20bytes  + 3  + 6  29bytes
            byte[] header = new byte[6]; // 获取头部报文，可以获取到后续报文的字节数
            socket.Receive(header, 0, 6, SocketFlags.None); // 从指定的socket中读取指定的字节数
            ushort len = BitConverter.ToUInt16(new byte[] { header[5], header[4] });  // 小端读取 - 读取后续还有多少字节数
            byte[] body = new byte[len]; // 实例化后续字节数
            int count = socket.Receive(body, 0, len, SocketFlags.None); // 从指定socket中读取后续字节数
            #endregion

            for (int i = 0; i < 10; i++)  // 从第3个位置 读取10个寄存器
            {
                int start = (i * 2) + 3; // 计算寄存器索引
                byte[] dataBytes = new byte[] { body[start], body[start + 1] }; // 获取寄存器值

                short value = BitConverter.ToInt16(dataBytes.Reverse().ToArray(), 0); // 将16进制寄存器转换为10进制

                Console.WriteLine($">> {value}"); // 输出寄存器的值
            }
        }
        #endregion

        #region Modbus校验位计算方法
        /// <summary>
        /// Modbus校验位计算方法 - 在网上可以收到现成的
        /// </summary>
        /// <param name="data">数据信息</param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        static byte[] CRC16(byte[] data)
        {
            if (data == null || !data.Any())
                throw new ArgumentException("");

            #region 校验位算法
            //运算
            ushort crc = 0xFFFF;
            for (int i = 0; i < data.Length; i++)
            {
                crc = (ushort)(crc ^ (data[i]));
                for (int j = 0; j < 8; j++)
                {
                    crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ 0xA001) : (ushort)(crc >> 1);
                }
            }
            #endregion

            byte hi = (byte)((crc & 0xFF00) >> 8);  //高位置
            byte lo = (byte)(crc & 0x00FF);         //低位置

            return new byte[] { hi, lo };
        }
        #endregion

        #region Ascii格式报文的校验位计算方法
        /// <summary>
        /// Ascii格式报文的校验位计算方法
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        static byte LRC(byte[] value)
        {
            if (value == null) return 0x00;

            int sum = 0;
            for (int i = 0; i < value.Length; i++)
            {
                sum += value[i];
            }

            sum = sum % 256;  // 只拿低位的一个字节数据
            sum = 256 - sum;

            return (byte)sum;
        }
        #endregion

        #region 封装后的协议测试方法
        static ModbusMaster modbus = new ModbusRTU();
        //static ModbusMaster modbus = new ModbusTCP();
        /// <summary>
        /// 封装后的协议测试方法
        /// </summary>
        static void ZxLibTest()
        {
            try
            {
                // 实例化协议对象
                //ModbusMaster modbus = new ModbusTCP();
                //ModbusMaster modbus = new ModbusRTU();
                //ModbusMaster modbus = new ModbusASCII();
                modbus.Connect(); // 连接
                #region 同步读取数据
                { // 同步读取数据
                    #region 读取short类型 - 实例
                    // short类型存储空间 1寄存器-》2个字节
                    //byte[] result = modbus.Read(1, ModbusLib.Base.Functions.RHoldingRegister, 0, 5); // 读取寄存器
                    ushort[] values = modbus.GetDatas<ushort>(result, EndianType.ABCD); // 大端
                    #endregion

                    #region 读取int类型- 实例
                    // int类型存储空间 2个寄存器-》4个字节
                    //int类型 6个寄存器-》12个字节-》3个长整型
                    //byte[] result = modbus.Read(1, ModbusLib.Base.Functions.RHoldingRegister, 0, 6);
                    //uint[] values = modbus.GetDatas<uint>(result, EndianType.ABCD); // 大端
                    //uint[] values = modbus.GetDatas<uint>(result, EndianType.DCBA); // 小端
                    #endregion

                    #region 读取double类型- 实例
                    // double类型存储空间 4个寄存器-》8个字节
                    //byte[] result = modbus.Read(1, ModbusLib.Base.Functions.RHoldingRegister, 0, 8); // 读取寄存器
                    //double[] values = modbus.GetDatas<double>(result, EndianType.ABCD); // 大端
                    #endregion

                    #region 读取线圈实例
                    //byte[] result = modbus.Read(1, ModbusLib.Base.Functions.RCoilStatus, 0, 5); // 读取寄存器
                    //bool[] values = ModbusMaster.GetDatas<bool>(result, EndianType.ABCD); // 大端
                    #endregion

                    #region 显示读取到的数据
                    //// 16进制方式显示数据
                    //Console.WriteLine(string.Join("-", result.Select(b => b.ToString("X2"))));
                    //// 十进制方式显示数据
                    //Console.WriteLine(string.Join("-", values.Select(v => v.ToString())));
                    #endregion
                }
                #endregion

                #region 异步读取数据
                {
                    #region 异步读取寄存器方法
                    // 读写同步  ： 总线同一时刻   只能有一个报文
                    //modbus.ReadAsync(1, Functions.RHoldingRegister
                    //    , 0, 5, 123, ReadCompleted);
                    #endregion
                }
                #endregion

                #region 使用功能码读取数据
                {
                    byte[] result = modbus.Read(1, "40001", false); // 读取一个寄存器
                    ushort[] values = ModbusMaster.GetDatas<ushort>(result);

                    #region 显示读取到的数据
                    // 16进制方式显示数据
                    Console.WriteLine(string.Join("-", result.Select(b => b.ToString("X2"))));
                    // 十进制方式显示数据
                    Console.WriteLine(string.Join("-", values.Select(v => v.ToString())));
                    #endregion
                }
                #endregion

                #region 同步写入数据实例
                { // 同步写入数据
                  //modbus.Write(1, ModbusLib.Base.Functions.WCoilStatus, 0, 5, new byte[] { 0x1C }); // 写入线圈
                  //modbus.Write(1, ModbusLib.Base.Functions.WHoldingRegister, 0, 5,
                  //    new byte[] { 0x00, 0x78, 0x00, 0x79, 0x00, 0x7A, 0x00, 0x7B, 0x00, 0x7C }); // 写入寄存器

                    #region 写入线圈实例
                    // 0001 1100
                    // 0x1C
                    //List<bool> values = new List<bool> { true, false, true, false, true, false, false, false, true, true };
                    //byte[] vb = modbus.GetBytes<bool>(values.ToArray());
                    //modbus.Write(1, ModbusLib.Base.Functions.WCoilStatus, 0,
                    //    (ushort)values.Count,
                    //    vb);
                    #endregion

                    #region 写入寄存器实例
                    // int、float是4个字节2个寄存器
                    //List<uint> values = new List<uint>() { 1200, 1500 }; // 整数
                    //List<float> values = new List<float>() { 1.23f, 4.56f }; // 小数
                    // 将十进制数据转换成字节数组
                    //byte[] vb = ModbusMaster.GetBytes<float>(values.ToArray(), EndianType.ABCD);
                    // (byte)(vb.Length / 2) 计算寄存器个数
                    //modbus.Write(1, Functions.WHoldingRegister, 0,
                    //    (byte)(vb.Length / 2),
                    //    vb);
                    #endregion
                }
                #endregion

                #region 异步写入数据实例
                {
                    //// 异步写
                    //List<ushort> values = new List<ushort>() { (ushort)1200, (ushort)1500 }; // 要写入的寄存器数据
                    //byte[] reqDatas = ModbusMaster.GetBytes<ushort>(values.ToArray()); // 将要写入的数据转成字节数组
                    //// 异步写入寄存器
                    //modbus.WriteAsync(1, Functions.WHoldingRegister
                    //    , 0, 2, reqDatas, 456, WriteCompleted);
                }
                #endregion

                #region 使用功能码写入数据
                {
                    List<ushort> vs = new List<ushort>() { 111, 222 }; // 写入2个寄存器
                    byte[] dataBytes = ModbusMaster.GetBytes<ushort>(vs.ToArray());
                    modbus.Write(1, "40001", 2, dataBytes);
                }
                #endregion
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

       /// <summary>
       /// Modbus协议解决同步读的回调方法
       /// </summary>
       /// <param name="bytes">返回的报文数据</param>
       /// <param name="id">请求ID，用于区分请求者</param>
       /// <param name="ex">异常信息</param>
        static void ReadCompleted(byte[] bytes, int id, Exception ex)
        {
            Console.WriteLine(id); // 输出请求ID
            if (ex != null) // 判断是否异常
            {
                Console.WriteLine(ex.Message);
                return;
            }
            // 将返回的字节数组转换成10进制
            ushort[] values = ModbusMaster.GetDatas<ushort>(bytes);
            // 输出返回报文数据
            Console.WriteLine(string.Join("-", values.Select(v => v.ToString())));

            //Thread.Sleep(1000);

            //modbus.ReadAsync(1, ModbusLib.Base.Functions.RHoldingRegister,
            //    0, 5, 123, ReadCompleted);
        }

        /// <summary>
        /// Modbus协议异步写的回调方法
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="id"></param>
        /// <param name="ex"></param>
        static void WriteCompleted(byte[] bytes, int id, Exception ex)
        {
            Console.WriteLine(id);
            if (ex != null)
            {
                Console.WriteLine(ex.Message);
                return;
            }
        }
        #endregion
    }
}
